package com.duobaoyu.dby.supply.api.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.math.Money;
import com.duobaoyu.dby.channel.adapter.enums.ChannelCodeEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.config.GlobalErrorCodeEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.order.OrderStatusEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.refund.AfterSaleStatusEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.refund.AfterSaleTypeEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.refund.RefundStatusEnum;
import com.duobaoyu.dby.channel.adapter.util.ChannelCodeUtils;
import com.duobaoyu.dby.channel.boundary.bean.refund.param.AfterSaleAttributesParam;
import com.duobaoyu.dby.channel.boundary.bean.refund.param.AfterSaleVoucherParam;
import com.duobaoyu.dby.channel.boundary.bean.refund.result.*;
import com.duobaoyu.dby.channel.common.util.SupplyCollectionUtils;
import com.duobaoyu.dby.common.core.constant.StringPool;
import com.duobaoyu.dby.common.core.validator.ValidationUtils;
import com.duobaoyu.dby.common.mybatis.util.EnumUtils;
import com.duobaoyu.dby.supply.api.service.AfterSaleBizService;
import com.duobaoyu.dby.supply.api.service.AfterSaleBizServiceConverter;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderBO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderProductBO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderQueryInBO;
import com.duobaoyu.dby.supply.biz.client.boundary.refund.AfterSaleBoundaryClient;
import com.duobaoyu.dby.supply.biz.client.boundary.refund.bo.AfterSaleLogisticsBO;
import com.duobaoyu.dby.supply.biz.client.resource.ResourceApiClient;
import com.duobaoyu.dby.supply.biz.client.resource.bean.bo.AuditStrategyConfigBO;
import com.duobaoyu.dby.supply.biz.client.resource.bean.bo.OrderRuleConfigBO;
import com.duobaoyu.dby.supply.biz.constant.OperateSourceConstants;
import com.duobaoyu.dby.supply.biz.enums.AfterSaleSourceEnum;
import com.duobaoyu.dby.supply.biz.enums.OrderParentTypeEnum;
import com.duobaoyu.dby.supply.biz.exception.SupplyChainBizException;
import com.duobaoyu.dby.supply.biz.service.order.OrderService;
import com.duobaoyu.dby.supply.biz.service.refund.AfterSaleService;
import com.duobaoyu.dby.supply.biz.service.refund.AfterSaleStateMachine;
import com.duobaoyu.dby.supply.common.biz.bo.refund.*;
import com.duobaoyu.dby.supply.common.biz.remote.dto.refund.*;
import com.duobaoyu.dby.supply.common.biz.remote.dto.validation.constraints.AfterSaleValidationGroup;
import com.duobaoyu.dby.supply.common.biz.remote.to.refund.*;
import com.duobaoyu.dby.supply.core.common.exception.CustomAssert;
import com.duobaoyu.dby.supply.db.entity.refund.AfterSaleImage;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.duobaoyu.dby.common.core.constant.CommonConstants.ZERO;

/**
 * @author yw
 * @description 售后 service
 * @date 2023/9/27 10:47
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AfterSaleBizServiceImpl implements AfterSaleBizService {

    private final OrderService orderService;
    private final AfterSaleService afterSaleService;
    private final ResourceApiClient resourceApiClient;
    private final AfterSaleStateMachine afterSaleStateMachine;
    private final AfterSaleBoundaryClient afterSaleBoundaryClient;
    private final AfterSaleBizServiceConverter afterSaleBizServiceConverter;


    @Override
    public AfterSaleAttributesTO queryAfterSaleAttributes(AfterSaleAttributesDTO afterSaleAttributes) {
        List<AfterSaleAttributesDTO.Sku> skus = afterSaleAttributes.getSkus();

        List<String> orderSns = skus.stream().map(AfterSaleAttributesDTO.Sku::getOrderSn).collect(Collectors.toList());
        OrderQueryInBO query = new OrderQueryInBO();
        query.setOrderSnList(orderSns);
        query.setParentType(OrderParentTypeEnum.AFTER_SPLIT.getValue());
        query.setReturnProduct(true);
        List<OrderBO> orders = orderService.list(query);
        CustomAssert.notEmpty(orders, "暂无订单信息");

        queryAfterSaleAttributesCheck(orders, skus);

        List<AfterSaleAttributesResult.Attribute> result = Lists.newArrayList();
        List<AfterSaleAttributesResult.Attribute> fastReturnAttributes = fastReturnAfterSaleAttributes(orders, skus);
        orders = orders.stream().filter(OrderBO::getAfterSalesAllowed).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(orders)) {
            List<AfterSaleItemOutBO> appliedAfterSale = findAppliedAfterSale(orderSns);
            List<AfterSaleAttributesResult.Attribute> attributeList
                    = appliedFastReturnAfterSaleAttributes(appliedAfterSale, orders, skus);
            if (CollectionUtils.isNotEmpty(attributeList)) {
                result.addAll(fastReturnAttributes);
            }
            if (CollectionUtils.isNotEmpty(skus)) {
                List<AfterSaleAttributesParam> params = convertAfterSaleAttributes(orders, skus, appliedAfterSale);
                List<AfterSaleAttributesResult> attributesResults = afterSaleBoundaryClient.afterSaleAttributesQuery(params);
                Optional<AfterSaleAttributesResult> errorResult = attributesResults.stream().filter(e -> !e.getSuccess()).findAny();
                errorResult.ifPresent(afterSaleAttributesResult -> SupplyChainBizException.fail(afterSaleAttributesResult.getErrorMessage()));

                List<AfterSaleAttributesResult.Attribute> attributes = attributesResults
                        .stream()
                        .flatMap(e -> CollectionUtils.isNotEmpty(e.getAttributes()) ? e.getAttributes().stream() : null)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());
                result.addAll(attributes);
            }
        }
        result.addAll(fastReturnAttributes);

        List<AfterSaleAttributesTO.Attribute> attributes = Lists.newArrayList();
        for (AfterSaleAttributesResult.Attribute attribute : result) {
            AfterSaleAttributesTO.Attribute attr = new AfterSaleAttributesTO.Attribute();
            attr.setPermitRetNum(attribute.getPermitRetNum());
            attr.setPermitChgNum(attribute.getPermitChgNum());
            attr.setSkuCode(attribute.getSkuCode());
            attr.setPickupProducts(attribute.getPickUpWays());
            attr.setAfterSaleTypes(attribute.getAfterSaleTypes());
            String orderSn = orders.stream().filter(item -> Objects.equals(item.getChannelOrderSn(), attribute.getChannelOrderSn()) && Objects.equals(item.getChannelCode(), attribute.getChannelCode())).map(OrderBO::getOrderSn).findFirst().orElse(null);
            attr.setOrderSn(orderSn);
            attr.setAfterSaleReasons(afterSaleBizServiceConverter.assembleAfterSaleReasons(attribute.getAfterSaleReasons()));
            attributes.add(attr);
        }
        return AfterSaleAttributesTO.builder().attributes(attributes).build();
    }

    private List<AfterSaleAttributesResult.Attribute> appliedFastReturnAfterSaleAttributes(List<AfterSaleItemOutBO> afterSaleItems,
                                                                                           List<OrderBO> orders,
                                                                                           List<AfterSaleAttributesDTO.Sku> skus) {
		Map<String, AfterSaleItemOutBO> afterSaleItemOutMap = Maps.newHashMap();
		if (CollectionUtils.isNotEmpty(afterSaleItems)) {
			afterSaleItemOutMap = afterSaleItems.stream()
					.filter(e -> EnumUtils.neq(AfterSaleStatusEnum.COMPLETED, e.getAfterSaleStatus()))
					.collect(Collectors.toMap(AfterSaleItemOutBO::getSkuCode, Function.identity(), (k1, k2) -> k1));
		}
        Map<String, OrderBO> orderMap = orders.stream()
                .collect(Collectors.toMap(OrderBO::getOrderSn, Function.identity(), (k1, k2) -> k1));

        List<AfterSaleAttributesResult.Attribute> attributes = Lists.newArrayList();
        Iterator<AfterSaleAttributesDTO.Sku> iterator = skus.iterator();
        while (iterator.hasNext()) {
            AfterSaleAttributesDTO.Sku sku = iterator.next();
            AfterSaleItemOutBO afterSaleItemOut = afterSaleItemOutMap.get(sku.getSkuCode());
            OrderBO order = orderMap.get(sku.getOrderSn());
            if (Objects.nonNull(afterSaleItemOut) && Objects.nonNull(order)) {
                attributes.add(this.getAttribute(order, sku));
                iterator.remove();
            }
			// 前提在于订单不会将 afterSaleAllowed 从 true 改为 false
			if (Objects.nonNull(order.getConfirmOrderTime())) {
				OrderRuleConfigBO orderRuleConfig = resourceApiClient.queryOrderRuleConfig(order.getChannelCode());
				OrderRuleConfigBO.OrderRule orderRule = orderRuleConfig.getOrderRule();
				if (Objects.nonNull(orderRule)) {
					Integer scopeTime = orderRule.getOrderConfirmNotAfterSaleTime();
					if (LocalDateTime.now().
							isAfter(order.getConfirmOrderTime().plusDays(Objects.isNull(scopeTime) ? ZERO : scopeTime))) {
						attributes.add(this.getAttribute(order, sku));
						iterator.remove();
					}
				}
			}
        }
        return attributes;
    }

    /**
     * 获取已申请的售后明细
     *
     * @param orderSns
     * @return
     */
    private List<AfterSaleItemOutBO> findAppliedAfterSale(List<String> orderSns) {
        AfterSaleItemQuery query = new AfterSaleItemQuery();
        query.setOrderSns(orderSns);
        query.setExcludeAfterSaleStatusList(Lists.newArrayList(AfterSaleStatusEnum.CLOSE.getValue(),
                AfterSaleStatusEnum.FAIL.getValue()));
        return afterSaleService.queryAfterSaleItems(query);
    }

    private List<AfterSaleAttributesResult.Attribute> fastReturnAfterSaleAttributes(List<OrderBO> orders, List<AfterSaleAttributesDTO.Sku> skus) {
        List<AfterSaleAttributesResult.Attribute> attributes = Lists.newArrayList();
        Map<String, OrderBO> orderMap = orders.stream().collect(Collectors.toMap(OrderBO::getOrderSn, Function.identity(), (v1, v2) -> v2));
        Map<String, List<AfterSaleAttributesDTO.Sku>> skuGroup = skus.stream().collect(Collectors.groupingBy(AfterSaleAttributesDTO.Sku::getOrderSn));
        skuGroup.forEach((k, v) -> {
            OrderBO order = orderMap.get(k);
            if (!order.getAfterSalesAllowed()) {
                List<AfterSaleAttributesResult.Attribute> data = v.stream()
                        .map(e -> getAttribute(order, e)).collect(Collectors.toList());
                attributes.addAll(data);
            }
        });
        return attributes;
    }

    private AfterSaleAttributesResult.Attribute getAttribute(OrderBO order, AfterSaleAttributesDTO.Sku sku) {
        AfterSaleAttributesResult.Attribute attribute = new AfterSaleAttributesResult.Attribute();
        attribute.setChannelCode(order.getChannelCode());
        attribute.setOrderTradeSn(order.getOrderTradeSn());
        attribute.setChannelOrderSn(order.getChannelOrderSn());
        attribute.setPermitRetNum(ZERO);
        attribute.setPermitChgNum(ZERO);
        attribute.setSkuCode(sku.getSkuCode());
        attribute.setPickUpWays(Collections.emptyList());
        attribute.setAfterSaleTypes(Collections.emptyList());
        attribute.setAfterSaleReasons(Collections.emptyList());
        return attribute;
    }

    private List<AfterSaleAttributesParam> convertAfterSaleAttributes
            (List<OrderBO> orders, List<AfterSaleAttributesDTO.Sku> skus, List<AfterSaleItemOutBO> afterSales) {


        Map<String, OrderBO> orderMap = orders.stream()
                .collect(Collectors.toMap(OrderBO::getOrderSn, Function.identity(), (v1, v2) -> v2));

        Map<String, OrderProductBO> orderProductMap = orders.stream().flatMap(e -> e.getOrderProductList().stream())
                .collect(Collectors.toMap(e -> e.getOrderSn() + e.getSkuCode(),
                        Function.identity()));

        Map<String, Integer> afterSaleProductMap = afterSales.stream()
                .collect(Collectors.groupingBy(AfterSaleItemOutBO::getSkuCode,
                        Collectors.reducing(ZERO, AfterSaleItemOutBO::getApplyNum, Integer::sum)));

        List<AfterSaleAttributesParam> params = Lists.newArrayList();
        Map<String, List<AfterSaleAttributesDTO.Sku>> skuGroup = skus.stream()
                .collect(Collectors.groupingBy(AfterSaleAttributesDTO.Sku::getOrderSn));
        skuGroup.forEach((k, v) -> {
            AfterSaleAttributesParam param = new AfterSaleAttributesParam();
            OrderBO order = orderMap.get(k);
            param.setChannelCode(order.getChannelCode());
            param.setChannelOrderSn(StringUtils.isEmpty(order.getChannelOrderSn()) ? order.getChannelTradeOrderSn() : order.getChannelOrderSn());
            param.setChannelPreOrderSn(StringUtils.isBlank(order.getChannelOrderParentSn()) ? param.getChannelCode() : order.getChannelOrderParentSn());
            param.setOrderTradeSn(order.getOrderTradeSn());
            param.setOrderStatus(order.getOrderStatus());
            List<AfterSaleAttributesParam.Sku> skuList = v.stream().map(e -> {
                AfterSaleAttributesParam.Sku skuInfo = new AfterSaleAttributesParam.Sku();
                OrderProductBO orderProduct = orderProductMap.get(e.getOrderSn() + e.getSkuCode());
                skuInfo.setChannelSkuCode(orderProduct.getChannelSkuCode());
                skuInfo.setOrderQuantity(orderProduct.getSkuNum());
                skuInfo.setCloseQuantity(Optional.ofNullable(orderProduct.getChannelReturnSkuNum()).orElse(ZERO));
                skuInfo.setAfterSaleQuantity(Optional.ofNullable(afterSaleProductMap.get(e.getSkuCode())).orElse(ZERO));
                skuInfo.setRepositoryType(orderProduct.getRepositoryType());
                skuInfo.setSkuCode(orderProduct.getSkuCode());
                return skuInfo;
            }).collect(Collectors.toList());
            param.setSkuList(skuList);
            params.add(param);
        });
        return params;
    }


    private static void queryAfterSaleAttributesCheck
            (List<OrderBO> orders, List<AfterSaleAttributesDTO.Sku> skus) {
        Map<String, Integer> orderStatusMap = orders.stream().collect(Collectors.toMap(OrderBO::getOrderSn, OrderBO::getOrderStatus));
        List<OrderProductBO> orderItems = orders.stream().flatMap(e -> e.getOrderProductList().stream()).collect(Collectors.toList());
        Map<String, OrderProductBO> map = orderItems.stream().collect(Collectors.toMap(e -> e.getOrderSn() + StringPool.DASHED + e.getSkuCode(), Function.identity()));
        skus.forEach(sku -> {
            Integer status = orderStatusMap.get(sku.getOrderSn());
            CustomAssert.notNull(status, String.format("订单号[%s]参数错误！", sku.getOrderSn()));
            CustomAssert.isTrue(EnumUtils.neq(OrderStatusEnum.WAIT_PAY, status) &&
                    EnumUtils.neq(OrderStatusEnum.CANCEL, status) &&
                    EnumUtils.neq(OrderStatusEnum.FAILURE, status), String.format("订单号[%s]不允许该操作！", sku.getOrderSn()));
            OrderProductBO item = map.get(sku.getOrderSn() + StringPool.DASHED + sku.getSkuCode());
            CustomAssert.notNull(item, String.format("订单号[%s]:sku编码[%s]参数错误！", sku.getOrderSn(), sku.getSkuCode()));
        });
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApplyAfterSaleTO createAfterSale(AfterSaleApplyDTO afterSaleApply) {
        Integer afterSaleType = afterSaleApply.getAfterSaleType();
        CustomAssert.notNull(AfterSaleTypeEnum.getAfterSaleTypeEnum(afterSaleType), "售后类型参数错误");
        if (EnumUtils.neq(AfterSaleTypeEnum.REFUND, afterSaleType)) {
            ValidationUtils.validateEntity(afterSaleApply, AfterSaleValidationGroup.PickUp.class, AfterSaleValidationGroup.ReturnWare.class);
        }
        List<AfterSaleApplyDTO.Content> contents = afterSaleApply.getContents();
        CustomAssert.isTrue(contents.size() == contents.stream()
                .map(AfterSaleApplyDTO.Content::getOrderSn).distinct().count(), "参数有误，重复订单号！");
        // 图片 check
        checkAfterSaleImage(contents);

		List<String> orderSns = contents.stream().map(AfterSaleApplyDTO.Content::getOrderSn)
				.distinct().collect(Collectors.toList());
		OrderQueryInBO query = new OrderQueryInBO();
		query.setOrderSnList(orderSns);
		query.setSupplyTradeSn(afterSaleApply.getSupplyTradeSn());
		query.setParentType(OrderParentTypeEnum.AFTER_SPLIT.getValue());
		query.setReturnProduct(true);
		List<OrderBO> orders = orderService.list(query);
		CustomAssert.notEmpty(orders, "暂无订单数据！");

		createAfterSaleCheck(orders, contents);
		AfterSaleCreateSplitInBO inputData = afterSaleBizServiceConverter.convertAfterSaleCreateSplit(afterSaleApply, orders);
		inputData.setAfterSaleSource(AfterSaleSourceEnum.USER.getValue());
		List<AfterSaleCreateInBO> afterSales = afterSaleService.afterSaleSplit(inputData, orders);
		populateAfterSaleAuditConfig(afterSales);
		AfterSaleApplyOutBO outData = afterSaleService.afterSaleApply(afterSales);
		return afterSaleBizServiceConverter.assembleApplyAfterSale(outData);
    }

    private void checkAfterSaleImage(List<AfterSaleApplyDTO.Content> contents) {
        Set<String> questionPicList = contents.stream()
                .map(item -> item.getSkus().stream()
                        .map(AfterSaleApplyDTO.Sku::getQuestionPic)
                        .filter(CollectionUtils::isNotEmpty)
                        .flatMap(Collection::stream)
                        .collect(Collectors.toList()))
                .flatMap(Collection::stream)
                .map(DigestUtils::md5Hex)
                .collect(Collectors.toSet());
        //仅退款不需要凭证,如果有传入凭证则校验是否在在库中存储
        if (CollectionUtils.isNotEmpty(questionPicList)) {
            int count = afterSaleService.selectImageCount(questionPicList);
            CustomAssert.isTrue(questionPicList.size() == count, "售后凭证不存在");
        }
    }

    private void createAfterSaleCheck(List<OrderBO> orders, List<AfterSaleApplyDTO.Content> contents) {
        Map<String, OrderBO> orderMap = orders.stream().collect(Collectors.toMap(OrderBO::getOrderSn, Function.identity()));
        List<OrderProductBO> orderItems = orders.stream().flatMap(e -> e.getOrderProductList().stream()).collect(Collectors.toList());

        Map<String, OrderProductBO> orderItemMap = orderItems.stream().collect
                (Collectors.toMap(e -> e.getOrderSn() + StringPool.COLON + e.getSkuCode(), Function.identity()));

        List<AfterSaleItemOutBO> afterSaleItems = findAppliedAfterSale(contents.stream().map(AfterSaleApplyDTO.Content::getOrderSn)
                .collect(Collectors.toList()));

        Set<String> itemInApplyMap = afterSaleItems.stream()
                .filter(e -> EnumUtils.neq(AfterSaleStatusEnum.COMPLETED, e.getAfterSaleStatus()))
                .map(AfterSaleItemOutBO::getSkuCode)
                .collect(Collectors.toSet());

        // 已申请成功的数量
        Map<String, Integer> appliedSuccessItemNumMap = afterSaleItems.stream()
                .filter(e -> EnumUtils.eq(AfterSaleStatusEnum.COMPLETED, e.getAfterSaleStatus()))
                .collect(Collectors.groupingBy(AfterSaleItemOutBO::getOrderSn,
                        Collectors.reducing(ZERO, AfterSaleItemOutBO::getApplyNum, Integer::sum)));

        contents.forEach(e -> {
            OrderBO order = orderMap.get(e.getOrderSn());
            CustomAssert.notNull(order, String.format("订单号[%s]参数错误！", e.getOrderSn()));
            if (Objects.nonNull(order.getConfirmOrderTime())) {
                OrderRuleConfigBO orderRuleConfig = resourceApiClient.queryOrderRuleConfig(order.getChannelCode());
                OrderRuleConfigBO.OrderRule orderRule = orderRuleConfig.getOrderRule();
                if (Objects.nonNull(orderRule)) {
                    Integer scopeTime = orderRule.getOrderConfirmNotAfterSaleTime();
                    if (LocalDateTime.now().
                            isAfter(order.getConfirmOrderTime().plusDays(Objects.isNull(scopeTime) ? ZERO : scopeTime))) {
                        SupplyChainBizException.fail(String.format("订单号[%s]超过售后期限", e.getOrderSn()));
                    }
                }
            }
            // 渠道订单号不存在时不允许售后
            CustomAssert.isTrue(order.getAfterSalesAllowed(), String.format("订单号[%s]不允许申请售后！", e.getOrderSn()));
            // 订单状态
            CustomAssert.isTrue(EnumUtils.neq(OrderStatusEnum.WAIT_PAY, order.getOrderStatus()) &&
                    EnumUtils.neq(OrderStatusEnum.CANCEL, order.getOrderStatus()) &&
                    EnumUtils.neq(OrderStatusEnum.FAILURE, order.getOrderStatus()), String.format("订单号[%s]不允许申请售后！", e.getOrderSn()));
            List<AfterSaleApplyDTO.Sku> skus = e.getSkus();
            skus.forEach(sku -> {
                // 订单编号和sku编码是否正确
                CustomAssert.isTrue(orderItemMap.containsKey(e.getOrderSn() + StringPool.COLON + sku.getSkuCode()), String.format("订单号[%s]:sku编码[%s]参数错误！", e.getOrderSn(), sku.getSkuCode()));
                // 金额
                OrderProductBO orderProduct = orderItemMap.get(e.getOrderSn() + StringPool.COLON + sku.getSkuCode());
                if (Objects.nonNull(sku.getApplyRefundAmount())) {
                    Money itemPaidAmount = new Money(orderProduct.getChannelPayAmount()).divide(orderProduct.getSkuNum())
                            .multiply(sku.getApplyNum());
                    boolean result = itemPaidAmount.compareTo(new Money(sku.getApplyRefundAmount())) >= ZERO;
                    CustomAssert.isTrue(result, String.format("sku编码[%s]申请价格超过了限制！", sku.getSkuCode()));
                }
                // 判断商品是否在售后中
                CustomAssert.isTrue(!itemInApplyMap.contains(sku.getSkuCode()), String.format("sku编码[%s]已在售后中，不允许再次申请售后！", sku.getSkuCode()));
                // 数量
                int maximumOfApply = orderProduct.getSkuNum() - Optional.ofNullable(orderProduct.getChannelReturnSkuNum())
                        .orElse(ZERO)
                        - appliedSuccessItemNumMap.getOrDefault(e.getOrderSn(), ZERO);
                CustomAssert.isTrue(maximumOfApply >= sku.getApplyNum(), String.format("sku编码[%s]申请数量超过了限制！", sku.getSkuCode()));
            });
        });
    }

    private void populateAfterSaleAuditConfig(List<AfterSaleCreateInBO> afterSales) {
        List<String> channelCodes = afterSales.stream().map(AfterSaleCreateInBO::getChannelCode)
                .distinct()
                .collect(Collectors.toList());
        List<AuditStrategyConfigBO> auditStrategies = channelCodes.stream()
                .map(resourceApiClient::queryAuditStrategyConfig)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        Map<String, AuditStrategyConfigBO> channelAuditStrategyMap = auditStrategies.stream()
                .collect(Collectors.toMap(AuditStrategyConfigBO::getChannelCode, Function.identity(), (v1, v2) -> v2));
        afterSales.forEach(afterSale -> {
            // 审核配置
            AuditStrategyConfigBO auditStrategyConfig = channelAuditStrategyMap.get(afterSale.getChannelCode());
            if (Objects.nonNull(auditStrategyConfig)
                    && Objects.nonNull(auditStrategyConfig.getAuditStrategy())) {
                AfterSaleAuditRuleBO auditRule = new AfterSaleAuditRuleBO();
                AuditStrategyConfigBO.AuditStrategy auditStrategy = auditStrategyConfig.getAuditStrategy();
                auditRule.setAutoAudit(auditStrategy.getEnable());
                if (auditRule.getAutoAudit()) {
                    auditRule.setLateSubmitTime(TimeUnit.HOURS.toMillis(auditStrategy.getAutoSubmit().longValue()));
                }
                afterSale.setAuditRule(auditRule);
            }
        });
    }

    @Override
    public AfterSaleAddressTO queryAfterSaleAddress(AfterSaleAddressDTO afterSaleAddress) {

        AfterSaleOutBO afterSale = afterSaleService.queryAfterSale(AfterSaleQuery.builder()
                .afterSaleSn(afterSaleAddress.getAfterSaleSn())
                .build());
        CustomAssert.notNull(afterSale, "售后申请单号错误！");

        queryAfterSaleAddressCheck(afterSaleAddress, afterSale.getItems());

        if (StringUtils.isEmpty(afterSale.getChannelAfterSaleSn())) {
            return AfterSaleAddressTO.builder().build();
        }
		CustomerReturnAddressBO customerReturnAddress = afterSale.getCustomerReturnAddress();
		if (Objects.nonNull(customerReturnAddress)
				&& StringUtils.isNotBlank(customerReturnAddress.getChannelAfterServiceAddress())) {
			return afterSaleBizServiceConverter.assembleLocalAfterSaleAddress(customerReturnAddress);
		}
        return afterSaleBizServiceConverter.assembleLocalAfterSaleAddress(saveAndReturnAddress(afterSale));
    }

	private CustomerReturnAddressBO saveAndReturnAddress(AfterSaleOutBO afterSale) {
		List<AfterSaleReturnAddressResult> addressResults =
				afterSaleBoundaryClient.afterSaleAddressQuery(Lists.newArrayList(afterSale));
		AfterSaleReturnAddressResult addressResult = CollUtil.getFirst(addressResults);
		AfterSaleCustomerReturnInfoInBO returnInfoInBO = new AfterSaleCustomerReturnInfoInBO();
		returnInfoInBO.setId(afterSale.getId());
		CustomerReturnAddressBO customerReturnAddressBO =
				afterSaleBizServiceConverter.toCustomerReturnAddress(addressResult);
		returnInfoInBO.setCustomerReturnAddress(customerReturnAddressBO);
		afterSaleService.modifyCustomerReturnInfo(returnInfoInBO);
		return customerReturnAddressBO;
	}

	private void queryAfterSaleAddressCheck(AfterSaleAddressDTO afterSaleAddress, List<AfterSaleItemOutBO> afterSales) {
        Set<String> existAfterSales = afterSales.stream().map(AfterSaleItemOutBO::getAfterSaleSn).collect(Collectors.toSet());
        CustomAssert.isTrue(existAfterSales.contains(afterSaleAddress.getAfterSaleSn()), String.format("售后单号%s不存在", afterSaleAddress.getAfterSaleSn()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public AfterSaleWayBillTO createAfterSaleWayBill(AfterSaleWayBillDTO afterSaleWayBill) {
		AfterSaleQuery query = new AfterSaleQuery();
		query.setAfterSaleSn(afterSaleWayBill.getAfterSaleSn());
		AfterSaleOutBO afterSale = afterSaleService.queryAfterSale(query);
		CustomAssert.notNull(afterSale, "售后申请单号错误！");
		// 校验填写数量
		afterSaleWayBillNumCheck(afterSaleWayBill, afterSale);
		AfterSaleWayBillInBO waybill = afterSaleBizServiceConverter.convertAfterSaleWayBill(afterSaleWayBill, afterSale);
		AfterSaleStateContext context = new AfterSaleStateContext();
		context.setAfterSale(afterSale);
		context.setOperateSource(OperateSourceConstants.OPERATION_BUSINESS);
		if (channelNoAfterSaleInterface(afterSale.getChannelCode())
				&& (EnumUtils.eq(AfterSaleTypeEnum.EXCHANGE, afterSale.getAfterSaleType())
				|| EnumUtils.eq(AfterSaleTypeEnum.REPAIR, afterSale.getAfterSaleType()))) {
			context.setCurrentAfterSaleStatus(AfterSaleStatusEnum.TO_BE_CONFIRMED_BY_USER.getValue());
		} else {
			context.setCurrentAfterSaleStatus(AfterSaleStatusEnum.PENDING_RECEIPT.getValue());
		}
		context.setChangeContentOperate(AfterSaleStateContext.ChangeContentOperate.builder().afterSaleWayBill(waybill).build());
		context.setOperateSource(OperateSourceConstants.OPERATION_BUSINESS);
		// 持久化数据
		boolean result = afterSaleStateMachine.exe(context);
		if (result) {
			if (EnumUtils.eq(AfterSaleStatusEnum.TO_BE_SENT_BACK_BY_BUYER, afterSale.getStatus())
					|| EnumUtils.eq(AfterSaleStatusEnum.PENDING_RECEIPT, afterSale.getStatus())) {
				// 调用渠道接口
				AfterSaleWayBillResult wayBillResult = afterSaleBoundaryClient.backFillWaybill(afterSale, waybill);
				CustomAssert.isTrue(wayBillResult.getResult(), "渠道回填售后物流信息失败");
			}
		}
		CustomAssert.isTrue(result, "回填售后物流信息失败");
		return AfterSaleWayBillTO.builder()
				.afterSaleSn(afterSale.getAfterSaleSn())
				.result(result)
				.build();
    }

	/**
	 * 渠道是否有售后对接接口
	 *
	 * @param afterSaleChannel
	 * @return
	 */
	private Boolean channelNoAfterSaleInterface(String afterSaleChannel) {
		Set<ChannelCodeEnum> noAfterSaleApiChannel = ChannelCodeUtils.channelNoAfterSaleInterface();
		return noAfterSaleApiChannel.contains(ChannelCodeEnum.getChannelCodeEnumByCode(afterSaleChannel));
	}

    @Override
    public CancelAfterSaleApplyTO cancelAfterSale(CancelAfterSaleDTO cancelAfterSale) {
        AfterSaleQuery afterSaleQuery = new AfterSaleQuery();
        afterSaleQuery.setAfterSaleSn(cancelAfterSale.getAfterSaleSn());
        AfterSaleOutBO afterSale = afterSaleService.queryAfterSale(afterSaleQuery);
        CustomAssert.notNull(afterSale, "售后申单不存在");
        AfterSaleStateContext context = new AfterSaleStateContext();
        context.setAfterSale(afterSale);
        context.setCurrentAfterSaleStatus(AfterSaleStatusEnum.CLOSE.getValue());
        context.setOperateSource(OperateSourceConstants.OPERATION_BUSINESS);
        boolean result = afterSaleStateMachine.exe(context);
        if (result && EnumUtils.eq(AfterSaleStatusEnum.TO_BE_REVIEWED_BY_CHANNEL, afterSale.getStatus())) {
            RefundCancelResult refundCancelResult = afterSaleBoundaryClient.cancelOrder(afterSale, cancelAfterSale.getRemark());
            CustomAssert.notNull(refundCancelResult, GlobalErrorCodeEnum.CHANNEL_NOT_SUPPORT_AFTER_SALE_CANCEL);
            CustomAssert.isTrue(refundCancelResult.getSuccess(), "渠道取消售后单失败!");
        }
        CustomAssert.isTrue(result, "取消售后单失败!");
        return CancelAfterSaleApplyTO.builder()
                .afterSaleSn(cancelAfterSale.getAfterSaleSn())
                .cancelResult(result).build();
    }

    private void afterSaleWayBillNumCheck(AfterSaleWayBillDTO dto, AfterSaleOutBO afterSaleApply) {
        Map<String, Integer> itemApplyNumMap = afterSaleApply.getItems().stream()
                .collect(Collectors.toMap(AfterSaleItemOutBO::getSkuCode, AfterSaleItemOutBO::getApplyNum));

        List<AfterSaleWayBillOutBO> existWaybills = afterSaleService.queryAfterSaleWaybills(dto.getAfterSaleSn());
        existWaybills.forEach(waybill -> {
            String skuCode = waybill.getSkuCode();
            if (itemApplyNumMap.containsKey(skuCode)) {
                itemApplyNumMap.put(skuCode, itemApplyNumMap.get(skuCode) - waybill.getSkuNum());
            }
        });
        itemApplyNumMap.put(dto.getAfterSaleSn(), itemApplyNumMap.get(dto.getSkuCode()) - dto.getProductNum());
        itemApplyNumMap.forEach((sn, num) -> CustomAssert.isTrue(num >= ZERO, String.format("售后单号[%s]申请的商品已全部寄回，请勿重复提交！", sn)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ConfirmAfterSaleTO confirmAfterSale(ConfirmAfterSaleDTO confirmAfterSale) {
        AfterSaleQuery afterSaleQuery = new AfterSaleQuery();
        afterSaleQuery.setAfterSaleSn(confirmAfterSale.getAfterSaleSn());
        AfterSaleOutBO afterSale = afterSaleService.queryAfterSale(afterSaleQuery);
        CustomAssert.notNull(afterSale, "售后单不存在");

        boolean result = false;
        AfterSaleConfirmResult confirmResult = afterSaleBoundaryClient.confirmAfterSale(afterSale);
        if (Objects.nonNull(confirmResult) && confirmResult.getResult()) {
            AfterSaleStateContext context = new AfterSaleStateContext();
            context.setAfterSale(afterSale);
            context.setCurrentAfterSaleStatus(AfterSaleStatusEnum.COMPLETED.getValue());
            context.setOperateSource(OperateSourceConstants.OPERATION_BUSINESS);
            result = afterSaleStateMachine.exe(context);
        }
        return ConfirmAfterSaleTO.builder()
                .afterSaleSn(confirmAfterSale.getAfterSaleSn())
                .confirmResult(result)
                .build();
    }

    @Override
    public AfterSaleLogisticsTO queryAfterSaleLogistics(AfterSaleLogisticsDTO afterSaleLogistics) {
        AfterSaleQuery afterSaleQuery = new AfterSaleQuery();
        afterSaleQuery.setAfterSaleSn(afterSaleLogistics.getAfterSaleSn());
        AfterSaleOutBO afterSaleOut = afterSaleService.queryAfterSale(afterSaleQuery);
        CustomAssert.notNull(afterSaleOut, "未查询到售后申请信息");
        List<AfterSaleLogisticsTO.Logistics> logistics;
        AfterSaleLogisticsBO afterSaleLogisticsInfo = afterSaleBoundaryClient.queryAfterSaleLogistics(
                afterSaleOut.getChannelCode(), afterSaleOut.getAfterSaleSn(), afterSaleOut.getChannelOrderSn());
        logistics = afterSaleLogisticsInfo.getLogistics().stream().map(v -> AfterSaleLogisticsTO.Logistics
                        .builder()
                        .expressCompanyCode(v.getChannelExpressCompanyCode())
                        .expressCompanyName(v.getChannelExpressCompanyName())
                        .build())
                .collect(Collectors.toList());
        return AfterSaleLogisticsTO.builder()
                .afterSaleSn(afterSaleOut.getAfterSaleSn())
                .bizAfterSaleSn(afterSaleOut.getBizAfterSaleSn())
                .logistics(logistics).build();
    }

    @Override
    public ApplyAfterSaleDetailTO queryAfterSale(AfterSaleQueryDTO dto) {
        CustomAssert.isTrue(StringUtils.isNotBlank(dto.getAfterSaleSn()) || StringUtils.isNotBlank(dto.getBizAfterSaleSn()), "供应链售后单号,外部售后单号不能同时为空");

        AfterSaleOutBO afterSaleOut = afterSaleService.queryAfterSale(AfterSaleQuery.builder()
                .bizAfterSaleSn(dto.getBizAfterSaleSn())
                .afterSaleSn(dto.getAfterSaleSn())
                .build());

        ApplyAfterSaleDetailTO applyAfterSaleDetail = afterSaleBizServiceConverter.toApplyAfterSaleDetail(afterSaleOut);
        applyAfterSaleDetail.setRefundSuccess(EnumUtils.eq(RefundStatusEnum.REFUND_SUCCESS, afterSaleOut.getRefundStatus()));
        AfterSalePickUpBO afterSalePickUp = afterSaleService.queryPickUp(dto.getAfterSaleSn());
        applyAfterSaleDetail.setPickupType(Optional.ofNullable(afterSalePickUp).map(AfterSalePickUpBO::getPickupType).orElse(null));
        return applyAfterSaleDetail;
    }

    @Override
    public String uploadAfterSaleVoucher(String orderSn, MultipartFile file) {
        OrderQueryInBO query = new OrderQueryInBO();
        query.setOrderSnList(Lists.newArrayList(orderSn));
        List<OrderBO> orders = orderService.list(query);
        CustomAssert.notEmpty(orders, "订单信息不存在");
        OrderBO order = SupplyCollectionUtils.getSingleDataFirst(orders, OrderBO::new);
        AfterSaleVoucherResult result = null;
        try {
            AfterSaleVoucherParam param = new AfterSaleVoucherParam();
            param.setChannelCode(order.getChannelCode());
            param.setFile(file);
            result = afterSaleBoundaryClient.afterSaleVoucher(param);
        } catch (UnsupportedOperationException e) {
            SupplyChainBizException.fail("不支持该操作！");
        }
        String imageAbsoluteUrl = result.getImageAbsoluteUrl();
        AfterSaleImage afterSaleImage = new AfterSaleImage();
        afterSaleImage.setImage(imageAbsoluteUrl);
        afterSaleImage.setImageHash(DigestUtils.md5Hex(imageAbsoluteUrl));
        afterSaleService.saveAfterSaleImage(afterSaleImage);
        return imageAbsoluteUrl;
    }
}
